/*******************************************************************************
 *                                    ZLG
 *                         ----------------------------
 *                         innovating embedded platform
 *
 * Copyright (c) 2001-present Guangzhou ZHIYUAN Electronics Co., Ltd.
 * All rights reserved.
 *
 * Contact information:
 * web site:    https://www.zlg.cn
 *******************************************************************************/
/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "core/include/hc32f4a0_regs_cmu.h"
#include "core/include/hc32f4a0_regs_pwc.h"
#include "core/include/hc32f4a0_regs_sram.h"
#include "core/include/hc32f4a0_regs_efm.h"
#include "core/include/hc32f4a0_regs_gpio.h"
#include "core/include/hc32f4a0_pwc.h"
#include "core/include/hc32f4a0_sram.h"
#include "core/include/hc32f4a0_clk.h"
#include "driver/gpio/hc32f4a0_gpio.h"
#include "driver/efm/hc32f4a0_efm.h"
#include "common/hc32f4a0_errno.h"
#include "common/hc32f4a0_common.h"
#include <stdio.h>

/*******************************************************************************
 * Macro operate
 ******************************************************************************/
#define HRC_FREQ_MON()   (*((volatile unsigned int*)(0x40010684UL)))

/*******************************************************************************
 * Statement
 ******************************************************************************/
/* \brief 系统时钟频率，设置为启动不被初始化*/
__attribute__((section(".noinit"))) uint32_t __g_sys_core_clk;
/* \brief 内部高速时钟频率，设置为启动不被初始化*/
__attribute__((section(".noinit"))) uint32_t __g_hrc_freq;
/* \brief PCLK时钟频率*/
static uint32_t __g_pclk_freq;
/* \brief HCLK时钟频率*/
static uint32_t __g_hclk_freq;
/* 外部高速OSC频率(根据实际情况而定)*/
static uint32_t __g_xtal_freq;
/* \brief 以下变量用于保存寄存器初始值*/
static uint32_t __g_cmu_scfgr_tmp;
static uint32_t __g_cmu_ckswr_tmp;
static uint32_t __g_cmu_pllhcfgr_tmp;

/*******************************************************************************
 * Code
 ******************************************************************************/
/**
 * \brief 更新系统时钟
 */
void sys_core_clk_update(void){
    uint32_t tmp;
    uint32_t pll_n;
    uint32_t pll_p;
    uint32_t pll_m;

    /* 获取内部高速RC频率*/
    if (1UL == (HRC_FREQ_MON() & 1UL)) {
        __g_hrc_freq = HRC_16MHz_VALUE;
    }
    else {
        __g_hrc_freq = HRC_20MHz_VALUE;
    }

    /* 检查系统时钟源*/
    tmp = HC32F4A0_CMU->CKSWR & CMU_CKSWR_CKSW;
    switch (tmp) {
        case 0x00U:
            __g_sys_core_clk = __g_hrc_freq;
            break;
        case 0x01U:
            __g_sys_core_clk = MRC_VALUE;
            break;
        case 0x02U:
            __g_sys_core_clk = LRC_VALUE;
            break;
        case 0x03U:
            __g_sys_core_clk = __g_xtal_freq;
            break;
        case 0x04U:
            __g_sys_core_clk = XTAL32_VALUE;
            break;
        /* PLLH作为系统时钟*/
        case 0x05U:
            /* PLLH输出时钟 = ((PLLH时钟源 / PLLHM) * PLLN) / PLLHP */
            pll_p = (uint32_t)((HC32F4A0_CMU->PLLHCFGR >> 28) & 0x0FUL);
            pll_n = (uint32_t)((HC32F4A0_CMU->PLLHCFGR >> 8) & 0xFFUL);
            pll_m = (uint32_t)((HC32F4A0_CMU->PLLHCFGR >> 0) & 0x03UL);

            /* 获取PLLH的输入时钟源*/
            tmp = ((HC32F4A0_CMU->PLLHCFGR >> 7) & 0x01);
            if (tmp == 0) {
                __g_sys_core_clk = (__g_xtal_freq) / (pll_m + 1UL) * (pll_n + 1UL) / (pll_p + 1UL);
            } else {
                __g_sys_core_clk = (__g_hrc_freq) / (pll_m + 1UL) * (pll_n + 1UL) / (pll_p + 1UL);
            }

            break;
        default:
            break;
    }
    /* 获取 PCLK 频率*/
    __g_pclk_freq = __g_sys_core_clk >> ((HC32F4A0_CMU->SCFGR & CMU_SCFGR_PCLK1S) >> CMU_SCFGR_PCLK1S_POS);
    /* 获取 HCLK 频率*/
    __g_hclk_freq = __g_sys_core_clk >> ((HC32F4A0_CMU->SCFGR & CMU_SCFGR_HCLKS) >> CMU_SCFGR_HCLKS_POS);
}

/**
 * \brief 获取 PCLK 时钟
 */
uint32_t sys_clk_pclk_get(void){
    return __g_pclk_freq;
}

/**
 * \brief 获取 HCLK 时钟
 */
uint32_t sys_clk_hclk_get(void){
    return __g_hclk_freq;
}

/**
 * \brief 获取系统核心时钟
 */
uint32_t sys_clk_core_get(void){
    return __g_sys_core_clk;
}

/**
 * \brief 设置外部晶振频率
 */
void sys_clk_xtal_set(uint32_t xtal_freq){
    __g_xtal_freq = xtal_freq;
}

/**
 * \brief 启动/关闭 PLLH
 */
static int sys_clk_pllh_set(uint8_t pll_sta){
    int               ret     = 0;
    volatile uint32_t timeout = 0;

    if (IS_CLK_UNLOCKED() == FALSE) {
        return -EINVAL;
    }

    if ((pll_sta != CLK_PLLH_OFF) && (pll_sta != CLK_PLLH_ON)) {
        return -EINVAL;
    }

    if (pll_sta == CLK_PLLH_OFF) {
        if (CLK_SYSCLKSOURCE_PLLH == READ_REG8_BIT(HC32F4A0_CMU->CKSWR, CMU_CKSWR_CKSW)) {
            ret = -EPERM;
        } else {
            WRITE_REG8(HC32F4A0_CMU->PLLHCR, CLK_PLLH_OFF);
        }
    } else {
        WRITE_REG8(HC32F4A0_CMU->PLLHCR, CLK_PLLH_ON);

        ret = -ETIME;
        while (timeout <= CLK_PLLH_TIMEOUT) {
            if (HC32F4A0_CMU->OSCSTBSR & CMU_OSCSTBSR_PLLHSTBF) {
                ret = 0;
                break;
            }
            timeout++;
        }
    }

    return ret;
}

/**
 * \brief 初始化 PLLH
 */
int sys_clk_pllh_init(const struct hc32f4a0_clk_pll *p_pllh_cfg){
    int      ret;
    uint32_t vco_in;
    uint32_t vco_out;

    if (NULL == p_pllh_cfg) {
        return -EINVAL;
    } else {
       if ((!IS_CLK_PLLHM_DIV(p_pllh_cfg->pll_cfg_r_f.pll_m + 1UL)) ||
               (!IS_CLK_PLLHN_MULTI(p_pllh_cfg->pll_cfg_r_f.pll_n + 1UL)) ||
               (!IS_CLK_PLLHR_DIV(p_pllh_cfg->pll_cfg_r_f.pll_r + 1UL)) ||
               (!IS_CLK_PLLHP_DIV(p_pllh_cfg->pll_cfg_r_f.pll_p + 1UL)) ||
               (!IS_CLK_PLL_SRC(p_pllh_cfg->pll_cfg_r_f.pll_src))) {
           return -EINVAL;
       }

       vco_in  = ((CLK_PLLSRC_XTAL == p_pllh_cfg->pll_cfg_r_f.pll_src ?
                __g_xtal_freq : __g_hrc_freq) / (p_pllh_cfg->pll_cfg_r_f.pll_m + 1UL));
       vco_out = vco_in * (p_pllh_cfg->pll_cfg_r_f.pll_n + 1UL);

       if ((!IS_CLK_PLLH_VCO_IN(vco_in)) ||
                (!IS_CLK_PLLH_VCO_OUT(vco_out)) ||
                (!IS_CLK_PLLH_FREQ(vco_out/(p_pllh_cfg->pll_cfg_r_f.pll_r + 1UL))) ||
                (!IS_CLK_PLLH_FREQ(vco_out/(p_pllh_cfg->pll_cfg_r_f.pll_q + 1UL))) ||
                (!IS_CLK_PLLH_FREQ(vco_out/(p_pllh_cfg->pll_cfg_r_f.pll_p + 1UL)))) {
            return -EPERM;
        }

        if ((IS_CLK_UNLOCKED() == FALSE) ||
                (IS_CLK_PLLH_STATE(p_pllh_cfg->pll_sta) == FALSE)) {
            return -EINVAL;
        }
        WRITE_REG32(HC32F4A0_CMU->PLLHCFGR, p_pllh_cfg->pll_cfg_r);

        ret = sys_clk_pllh_set(p_pllh_cfg->pll_sta);
    }

    return ret;
}

#if 0
/**
 * \brief XTAL 设置为振荡器模式
 */
static void xtal_switch_to_osc(void){
    int Ret;
    Ret = (0x80) | (0 << 6);
    WRITE_REG8(HC32F4A0_CMU->XTALCFGR, Ret);
}

/**
 * \brief XTAL 设置为外部时钟输入模式
 */
static void xtal_switch_to_clk(void){
    int Ret;
    Ret = (0x80) | (1 << 6);
    WRITE_REG8(HC32F4A0_CMU->XTALCFGR, Ret);
}

/**
 * \brief 初始化 XTAL
 */
static int SystemClkXtalInit(void){
    uint32_t TimeOut = 1000;

    xtal_switch_to_osc();
    xtal_switch_to_clk();

    /* 设置稳定周期*/
    WRITE_REG8(HC32F4A0_CMU->XTALSTBCR, (9 << 0));
    /* XTAL 设置为高驱动能力*/
    WRITE_REG8(HC32F4A0_CMU->XTALCFGR, (0x80| (0 << 4)));
    /* 启动 XTAL*/
    WRITE_REG8(HC32F4A0_CMU->XTALCR, 0);
    /* 等待 XTAL 稳定*/
    while ((!(READ_REG8(HC32F4A0_CMU->OSCSTBSR) & 0x8)) && (--TimeOut));

    if (TimeOut == 0) {
        return -ETIME;
    }
    return 0;
}

/**
 * \brief 初始化 XTAL32
 */
static void SystemClkXtal32Init(void){
    uint32_t  Tmp;
    int       TimeOut = 0;

    xtal_switch_to_osc();
    /* 复位 PWC 备份域*/
    Tmp = 0xA5;
    WRITE_REG8(HC32F4A0_PWC->VBATRSTR, Tmp);
    /* XTAL32 中驱动能力*/
    WRITE_REG8(HC32F4A0_CMU->XTAL32CFGR, 0);
    /* RUN模式 /停止模式 /掉电模式, XTAL32的3us滤波有效*/
    WRITE_REG8(HC32F4A0_CMU->XTAL32NFR, 0);
    /* 启动 XTAL32 */
    WRITE_REG8(HC32F4A0_CMU->XTAL32CR, 0);

    while (TimeOut <= 0x1000UL) {
        TimeOut++;
    }
}
#endif
/**
 * \brief HC32f4a0 HCLK/PCLK 时钟分频设置
 */
int sys_clk_div_set(uint8_t clk_cate, uint32_t div){
    volatile uint32_t timeout  = 0;
    /* 备份 FCGx 设置 */
    volatile uint32_t fcg0     = HC32F4A0_PWC->FCG0;
    volatile uint32_t fcg1     = HC32F4A0_PWC->FCG1;
    volatile uint32_t fcg2     = HC32F4A0_PWC->FCG2;
    volatile uint32_t fcg3     = HC32F4A0_PWC->FCG3;
    uint8_t           flag_tmp = 0;

    /* 检查各个分频参数是否合法*/
    if ((IS_CLK_HCLK_DIV(div & CMU_SCFGR_HCLKS) == FALSE) ||
                    (IS_CLK_PCLK0_DIV(div & CMU_SCFGR_PCLK0S) == FALSE  ||
                    (IS_CLK_PCLK1_DIV(div & CMU_SCFGR_PCLK1S) == FALSE) ||
                    (IS_CLK_PCLK2_DIV(div & CMU_SCFGR_PCLK2S) == FALSE  ||
                    (IS_CLK_PCLK3_DIV(div & CMU_SCFGR_PCLK3S) == FALSE) ||
                    (IS_CLK_PCLK4_DIV(div & CMU_SCFGR_PCLK4S) == FALSE) ||
                    (IS_CLK_EXCLK_DIV(div & CMU_SCFGR_EXCKS) == FALSE)))) {
        return -EINVAL;
    }

    if ((IS_CLK_CATE(clk_cate) == FALSE) || (IS_CLK_UNLOCKED() == FALSE)) {
        return -EINVAL;
    }

    /* 当前系统时钟源或者目标系统时钟源是PLLH时，需要在切换系统时钟源期间先关闭FCG0~FCG3
     * 然后再重新打开FCG0~FCG3。所以需要先备份FCG0~FCG3的值*/
    if (CLK_SYSCLKSOURCE_PLLH == READ_REG8_BIT(HC32F4A0_CMU->CKSWR, CMU_CKSWR_CKSW)) {
        if ((HC32F4A0_PWC->FCG0PC & PWC_FCG0PC_PRT0) != PWC_FCG0PC_PRT0) {
            return -EPERM;
        }

        flag_tmp = 1;
        /* 关闭 FCGx. */
        HC32F4A0_PWC->FCG0 = CLK_FCG0_DEFAULT;
        HC32F4A0_PWC->FCG1 = CLK_FCG1_DEFAULT;
        HC32F4A0_PWC->FCG2 = CLK_FCG2_DEFAULT;
        HC32F4A0_PWC->FCG3 = CLK_FCG3_DEFAULT;

        /* 关闭 FCGx 后等待稳定 */
        do {
            timeout++;
        } while(timeout < CLK_SYSCLK_SW_STABLE);
    }

    /* PCLK0 分频*/
    if (0 != (CLK_CATE_PCLK0 & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_PCLK0S, div);
    }
    /* PCLK1 分频*/
    if (0 != (CLK_CATE_PCLK1 & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_PCLK1S, div);
    }
    /* PCLK2 分频*/
    if (0U != (CLK_CATE_PCLK2 & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_PCLK2S, div);
    }
    /* PCLK3 分频*/
    if (0 != (CLK_CATE_PCLK3 & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_PCLK3S, div);
    }
    /* PCLK4 div */
    if (0 != (CLK_CATE_PCLK4 & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_PCLK4S, div);
    }
    /* Ext. 总线时钟分配*/
    if (0U != (CLK_CATE_EXCLK & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_EXCKS, div);
    }
    /* HCLK 分频*/
    if (0U != (CLK_CATE_HCLK & clk_cate)) {
        MODIFY_REG32(HC32F4A0_CMU->SCFGR, CMU_SCFGR_HCLKS, div);
    }

    timeout = 0;
    do {
        timeout++;
    } while(timeout < CLK_SYSCLK_SW_STABLE);

    if (1 == flag_tmp) {
        HC32F4A0_PWC->FCG0 = fcg0;
        HC32F4A0_PWC->FCG1 = fcg1;
        HC32F4A0_PWC->FCG2 = fcg2;
        HC32F4A0_PWC->FCG3 = fcg3;

        /* 打开 FCGx 后等待稳定 */
        timeout = 0;
        do {
            timeout++;
        } while(timeout < CLK_SYSCLK_SW_STABLE);
    }

    return 0;
}

/**
 * \brief 设置系统时钟源
 */
void sys_clk_src_set(uint8_t clk_src){
    uint32_t tmp;
    uint32_t timeout  = 0;
    uint8_t  flag_tmp = 0;
    /* 备份FCG0~FCG3的值*/
    uint32_t fcg0    = HC32F4A0_PWC->FCG0;
    uint32_t fcg1    = HC32F4A0_PWC->FCG1;
    uint32_t fcg2    = HC32F4A0_PWC->FCG2;
    uint32_t fcg3    = HC32F4A0_PWC->FCG3;

    if ((clk_src != CLK_SYSCLKSOURCE_HRC) &&
            (clk_src != CLK_SYSCLKSOURCE_MRC) &&
            (clk_src != CLK_SYSCLKSOURCE_LRC) &&
            (clk_src != CLK_SYSCLKSOURCE_XTAL) &&
            (clk_src != CLK_SYSCLKSOURCE_XTAL32) &&
            (clk_src != CLK_SYSCLKSOURCE_PLLH)) {
        return;
    }

    /* 检查时钟控制寄存器是否被保护*/
    if ((IS_CLK_UNLOCKED() == FALSE)) {
        return;
    }

    /* 当前系统时钟源或者目标系统时钟源是PLLH时，需要在切换系统时钟源期间先关闭FCG0~FCG3
     * 然后再重新打开FCG0~FCG3。所以需要先备份FCG0~FCG3的值*/
    tmp = HC32F4A0_CMU->CKSWR & CMU_CKSWR_CKSW;
    if ((tmp == CLK_SYSCLKSOURCE_PLLH) || (clk_src == CLK_SYSCLKSOURCE_PLLH)) {
        flag_tmp = 1;
        if ((HC32F4A0_PWC->FCG0PC & PWC_FCG0PC_PRT0) != PWC_FCG0PC_PRT0) {
            return;
        }
        /* 关闭FCG0~FCG3寄存器*/
        HC32F4A0_PWC->FCG0 = CLK_FCG0_DEFAULT;
        HC32F4A0_PWC->FCG1 = CLK_FCG1_DEFAULT;
        HC32F4A0_PWC->FCG2 = CLK_FCG2_DEFAULT;
        HC32F4A0_PWC->FCG3 = CLK_FCG3_DEFAULT;

        /* 等待稳定*/
        do {
            timeout++;
        } while (timeout < CLK_SYSCLK_SW_STABLE);
    }

    /* 设置系统时钟源*/
    WRITE_REG8(HC32F4A0_CMU->CKSWR, clk_src);

    /* 等待设置系统时钟源稳定*/
    timeout = 0;
    do {
        timeout++;
    } while(timeout < CLK_SYSCLK_SW_STABLE);

    /* 重新打开FCG0~FCG3寄存器*/
    if (1 == flag_tmp) {
        HC32F4A0_PWC->FCG0 = fcg0;
        HC32F4A0_PWC->FCG1 = fcg1;
        HC32F4A0_PWC->FCG2 = fcg2;
        HC32F4A0_PWC->FCG3 = fcg3;

        /* 等待稳定*/
        timeout = 0;
        do {
            timeout++;
        } while (timeout < CLK_SYSCLK_SW_STABLE);
    }

    /* 更新系统时间源*/
    sys_core_clk_update();
}

/**
 * \brief 时钟寄存器值备份
 */
void sys_clk_reg_backup(void){
    /* 备份寄存器值，以便反初始化*/
    __g_cmu_ckswr_tmp    = READ_REG32(HC32F4A0_CMU->CKSWR) & CMU_CKSWR_CKSW;
    __g_cmu_scfgr_tmp    = READ_REG32(HC32F4A0_CMU->SCFGR);
    __g_cmu_pllhcfgr_tmp = READ_REG32(HC32F4A0_CMU->PLLHCFGR);

}

/**
 * \brief 时钟反初始化
 */
void sys_clk_reg_recover(void){
    /* 系统寄存器写使能*/
    sys_peripheral_we();
    /* 恢复时钟源*/
    sys_clk_src_set(__g_cmu_ckswr_tmp);
    /* 关闭 PLLH*/
    sys_clk_pllh_set(CLK_PLLH_OFF);
    /* 恢复 PLLH 倍频与分频*/
    WRITE_REG32(HC32F4A0_CMU->PLLHCFGR, __g_cmu_pllhcfgr_tmp);
    /* 恢复分频*/
    //WRITE_REG32(HC32F4A0_CMU->SCFGR, __g_cmu_scfgr_tmp);
    sys_clk_div_set(CLK_CATE_ALL, __g_cmu_scfgr_tmp);
    /* 系统寄存器写保护*/
    sys_peripheral_wp();
}
